﻿/*
  Copyright © Alexander G. Bykin, Russia 2010
  This source is subject to the Microsoft Public License (Ms-PL).
  Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  Project link: http://datagridriaex4sl.codeplex.com/
  Related project link: http://riadatafilter.codeplex.com/
  All other rights reserved.
*/

namespace DataGridRIAExtension
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Reflection;
    using System.ServiceModel.DomainServices.Client;
    using System.Windows.Controls;
    using System.Windows.Data;

    /// <summary>
    /// This class Part of 'RIA DataFilter control for Silverlight' source at http://riadatafilter.codeplex.com/
    /// </summary>
    public static class Utilities
    {
        #region Public Methods

        #region Public Method GetListOfDomainEntityFields

        /// <summary>
        /// Get the list of DomainEntityField from DependencyProperty FieldsSource using IncludeFieldList and ExcludeFieldList.
        /// </summary>
        /// <returns>Return List of DomainEntityField.</returns>
        public static List<DomainEntityField> GetListOfDomainEntityFields(DomainDataSource dds)
        {
            List<DomainEntityField> result = new List<DomainEntityField>();
            IDictionary<string, BindingMode> bindingModes = new Dictionary<string, BindingMode>();

            if (dds == null || string.IsNullOrWhiteSpace(dds.QueryName))
            {
                if (System.Diagnostics.Debugger.IsAttached)
                {
                    System.Diagnostics.Debug.WriteLine("DomainDataSource is Null or QueryName is empty, next work of 'GetListOfDomainEntityFields' method can't be done, exiting.");
                }
                return result;
            }

            #region Special thanks for Kyle McClellan

            var entityType = GetEntityTypeFromDomainContextByQueryName(dds.DomainContext, dds.QueryName);

            #endregion

            List<PropertyInfo> propertyInfos = entityType.GetEntityProperties();

            if (propertyInfos == null)
            {
                if (System.Diagnostics.Debugger.IsAttached)
                {
                    System.Diagnostics.Debug.WriteLine("EntityType is not have properties.");
                }
                return result;
            }

            if (System.Diagnostics.Debugger.IsAttached)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("Entity: '{0}'", entityType.Name));
            }

            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                if (propertyInfo.GetIndexParameters().Length > 0)
                {
                    // Don't generate anything for indexed properties.
                    continue;
                }

                DisplayAttribute displayAttribute = GetPropertyDisplayAttribute(entityType.Name, propertyInfo, bindingModes);

                string displayName = string.Empty;
                string propertyPath = string.Empty;

                //if RelatedEntity then Parse that, else that is ParentEntity field 
                if (propertyInfo.PropertyType.BaseType == typeof(Entity))
                {
                    string relatedEntityName = propertyInfo.Name; //PropertyName
                    string relatedEntityTypeName = propertyInfo.PropertyType.Name; //PropertyTypeName
                    if (string.IsNullOrWhiteSpace(relatedEntityTypeName))
                    {
                        if (System.Diagnostics.Debugger.IsAttached)
                        {
                            System.Diagnostics.Debug.WriteLine(string.Format("Cant get RelatedEntity TypeName of Entity '{0}'", propertyInfo.Name));
                        }
                    }
                    else
                    {
                        if (System.Diagnostics.Debugger.IsAttached)
                        {
                            System.Diagnostics.Debug.WriteLine(string.Format("RelatedEntity: Name='{0}', TypeName='{1}'", relatedEntityName, relatedEntityTypeName));
                        }

                        foreach (var relatedEntityField in propertyInfo.PropertyType.GetEntityProperties())
                        {
                            string relatedPropertyPath = string.Format("{0}.{1}", relatedEntityName, relatedEntityField.Name);
                            DisplayAttribute entityDisplayAttribute = GetPropertyDisplayAttribute(relatedEntityTypeName, relatedEntityField, bindingModes);
                            GetEntityFieldInfo(relatedEntityField, entityDisplayAttribute, out displayName, out propertyPath/*, out propertyType*/);

                            string debugMessage = string.Format("RelatedEntityField: displayName='{0}', propertyPath='{1}'", displayName, relatedPropertyPath);

                            debugMessage += " IsAdded";
                            result.Add(new DomainEntityField() { PropertyPath = relatedPropertyPath, DisplayName = displayName/*, PropertyType = propertyType*/ });

                            if (System.Diagnostics.Debugger.IsAttached)
                            {
                                System.Diagnostics.Debug.WriteLine(debugMessage);
                            }
                        }
                    }
                }
                else
                {
                    GetEntityFieldInfo(propertyInfo, displayAttribute, out displayName, out propertyPath/*, out propertyType*/);

                    string debugMessage = string.Format("EntityField: displayName='{0}', propertyPath='{1}'", displayName, propertyPath);

                    debugMessage += " IsAdded";
                    result.Add(new DomainEntityField() { PropertyPath = propertyPath, DisplayName = displayName/*, PropertyType = propertyType*/ });

                    if (System.Diagnostics.Debugger.IsAttached)
                    {
                        System.Diagnostics.Debug.WriteLine(debugMessage);
                    }
                }
            }

            return result;
        }

        #endregion

        #region Public Method GetListOfEntityTypeFromDomainContext

        public static List<Type> GetListOfEntityTypeFromDomainContext(DomainContext domainContext)
        {
            List<Type> result = new List<Type>();

            if (domainContext == null)
            {
                throw new Exception("DomainContext can not be null.");
            }

            foreach (EntitySet item in domainContext.EntityContainer.EntitySets)
            {
                result.Add(item.EntityType);
            }

            return result;
        }

        #endregion

        #region Public Method GetEntityTypeFromDomainContextByQueryName

        public static Type GetEntityTypeFromDomainContextByQueryName(DomainContext domainContext, string queryName)
        {
            queryName = (queryName.Contains("Query")) ? queryName : string.Format("{0}Query", queryName);

            MethodInfo operationInfo = domainContext.GetType().GetMethods().Where(m => (m.Name == queryName)).FirstOrDefault();

            if (operationInfo == null)
            {
                if (System.Diagnostics.Debugger.IsAttached)
                {
                    System.Diagnostics.Debug.WriteLine(string.Format("DomainContext is not have QueryMethod '{0}'.", queryName));
                }
                throw new Exception(string.Format("DomainContext is not have QueryMethod '{0}'.", queryName));
            }

            if (!typeof(EntityQuery).IsAssignableFrom(operationInfo.ReturnType))
            {
                throw new NotImplementedException("Support for return types other than EntityQuery is not implemented.");
            }

            #region Special thanks for Kyle McClellan

            return operationInfo.ReturnType.GetGenericArguments()[0];

            #endregion
        }

        #endregion

        #region Public Method GetPropertyDisplayAttribute

        /// <summary>
        /// Gets EntityField DisplayAttribute
        /// </summary>
        /// <param name="entityName">The name of Entity</param>
        /// <param name="propertyInfo">The EntityField</param>
        /// <param name="bindingModes">The list of Bindings</param>
        /// <returns></returns>
        public static DisplayAttribute GetPropertyDisplayAttribute(string entityName, PropertyInfo propertyInfo, IDictionary<string, BindingMode> bindingModes)
        {
            DisplayAttribute displayAttribute =
                    propertyInfo.GetCustomAttributes(typeof(DisplayAttribute), true) //inherit
                    .Cast<DisplayAttribute>()
                    .FirstOrDefault();

            if (displayAttribute == null ||
                !displayAttribute.GetAutoGenerateField().HasValue ||
                displayAttribute.GetAutoGenerateField().Value == true)
            {
                if (propertyInfo.GetSetMethod() != null)
                {
                    if (!bindingModes.ContainsKey(string.Format("{0}.{1}", entityName, propertyInfo.Name)))
                    {
                        bindingModes.Add(string.Format("{0}.{1}", entityName, propertyInfo.Name), BindingMode.TwoWay);
                    }
                }
                else
                {
                    if (!bindingModes.ContainsKey(string.Format("{0}.{1}", entityName, propertyInfo.Name)))
                    {
                        bindingModes.Add(string.Format("{0}.{1}", entityName, propertyInfo.Name), BindingMode.OneWay);
                    }
                }
            }

            return displayAttribute;
        }

        #endregion

        #region Public Method GetEntityFieldInfo

        /// <summary>
        /// Gets EntityField info: displayName, properyPath, propertyType
        /// </summary>
        /// <param name="propertyInfo"></param>
        /// <param name="domainEntityField"></param>
        /// <param name="displayAttribute"></param>
        /// <param name="displayName">out displayName</param>
        /// <param name="propertyPath">out propertyPath</param>
        /// <param name="propertyType">out propertyType</param>
        public static void GetEntityFieldInfo(PropertyInfo propertyInfo, DisplayAttribute displayAttribute, out string displayName, out  string propertyPath/*, out Type propertyType*/)
        {
            if (displayAttribute != null)
            {
                //GET Entity field name from DomainContext
                displayName = displayAttribute.GetName();
                if (string.IsNullOrEmpty(displayName))
                {
                    displayName = displayAttribute.GetShortName();
                }
                propertyPath = propertyInfo.Name;
            }
            else
            {
                displayName = propertyInfo.Name;
                propertyPath = propertyInfo.Name;
                //propertyType = propertyInfo.PropertyType;
            }
        }

        #endregion

        #region Public Method GetEntityProperties

        /// <summary>
        /// Gets the Entity fields
        /// </summary>
        /// <param name="type">EntityType</param>
        /// <returns>List of PropertyInfo</returns>
        public static List<PropertyInfo> GetEntityProperties(this Type type)
        {
            //riaservicescontrib.codeplex.com version that not return included entities
            /*BindingFlags bindingAttr = BindingFlags.Public | BindingFlags.Instance;
            var qry = from p in type.GetProperties(bindingAttr)
                      where p.GetCustomAttributes(typeof(DataMemberAttribute), true).Length > 0
                      && p.GetSetMethod() != null
                      select p;
            return qry.ToList();*/

            return type.GetProperties(BindingFlags.Public | BindingFlags.Instance).
                Where(r => r.CanRead && r.CanWrite && (r.PropertyType == typeof(bool) || r.PropertyType == typeof(int) || r.PropertyType == typeof(decimal) || r.PropertyType == typeof(byte) || r.PropertyType == typeof(string) || r.PropertyType == typeof(DateTime) || r.PropertyType.BaseType == typeof(Entity))).ToList();
        }

        #endregion

        #endregion
    }
}
